-- 2022-8-5

---[[ 诀窍
    -- 模式匹配是进行字符串处理的强大工具之一
    -- 虽然通过多次调用函数gsub就可以完成许多复杂的操作，但还是一个谨慎使用该函数

    -- 模式匹配替代不了传统的解析器
    -- 对于那些用后即弃的程序来说，我们确实可以在源代码中做一些有用的操作，但却很难构建出高质量的产品
    -- 例如之前的一个实例，如果C代码中有一个字符串常量 "/*" 那么就会得到错误的结果
    do
        local test = [[char s[] = "a /* here"; /* a tricky string */]]
        print((string.gsub(test,"/%*.-%*/","<COMMENT>")))
    end
    -- 由于含有注释标记的字符串很少见，因此对于我们自用的程序而言可能满足需求；但是我们不应该将这个带有缺陷的程序发布

    -- 通常。在Lua程序中使用的模式匹配时的效率是足够高的:新机器可以在不到0.2秒的时间内计算出一个4.4MB大小(具有85万个单词)的文本单词数量
    -- 备注 此处使用的新机器是 内存为8GB，CPU主频为3.6GHz的Inter Core i7-4790
    -- 但仍然需要注意的是我们应该永远使用尽可能精确的模式，不精确的模式的模式慢很多
    -- 一个极端的例子是 '(.-)%$' 他用于获取字符串中第一个$符号前的所有内容，如果目标字符串中有$ 那么这个工作很正常
    -- 但是若每有，那么模式匹配算法就会首先从字符串起始位置开始匹配，直到遍历完成，然后从第二个字符开始重复算法
    -- 从而导致O(n^2)的时间复杂度，这会导致超过四分钟的时间来计算
    -- 解决这个问题只需要 '^(.-)%$' 将模式锁定在字符串的开始位置即可
    -- 有了^的锚定之后该模式匹配只需要不到0.01秒的时间

    -- 此外，还要留心空位置，也就是那些匹配空字符串的模式，例如使用模式 '%a*' 会发现到处都是名字
    do
        local i,j = string.find(":$%  **#$hello13","%a*")
        print(i,j)
    end
    -- 在这个示例中，函数find在字符串的开始位置正确的找到一个空的字母序列

    -- 在模式的结束使用修饰符-是没有意义的，因为这样之后匹配到空字符串，该修饰符总是需要在其后跟上其他的东西来限制扩展的范围
    -- 同样 '.*' 的模式也非常容易出错，主要是因为这种模式会匹配到超乎我们预期范围的内容

    -- 有时,用Lua语言来构造一个模式也很有用。我们已经在将空格转换为制表符的程序中使用过这个技巧
    -- 接下来再看另外一个示例，考虑如何找出一个文本中较长的行(比如超过70个字符的行)
    -- 较长的行就是一个具有70个或更多字符的序列，其中每个字符都不换为换行符，因而可以用字符分类 '[^\n]'来匹配出换行符以外的其他单个字符
    -- 除了手写以外，还可以使用函数 string.rep 来创建这个模式
    local pattern = string.rep("[^\n]",70) .. "+"
    -- 再举一个例子，假设进行大小写无关的查找
    -- 一种方法是将模式中的所有字母x用 '[xX]' 替换，即同时包含原字母大小写形式的字符分类
    -- 我们可以用如下函数来自动的完成这种转换
    do
        function nocase(s)
            s = string.gsub(s,"%a",function (c)
                return "[" .. string.lower(c) .. string.upper(c) .. "]"
            end)
            return s
        end
    end
    print(nocase("Hi there!"))
    -- 有时，我们可能需要将所有出现的s1替换为s2，而不管其中是否包含魔法字符
    -- 如果字符串s1和s2是常量，那么可以在编写字符串时对魔法字符进行合理转义
    -- 但如果字符串是一个变量，那么就需要另一个gsub函数来进行转义
    do
        local s1 = string.gsub("gs ub","%W","%%%1")
        local s2 = string.gsub("gsu%b","%%","%%%%")
        print(s1,s2)
    end
    -- 在进行字符串搜索时，我们对所有数字和字母外的字符进行了转义(即大写的W)，而在替换字符串中，我们只对百分号进行了转义
    
    -- 模式匹配的另一个有用的技巧是，在进行实际工作前先对目标字符串进行预处理
    -- 假设想把一个字符串中所有被双引号括起来的内容改为大写，但又允许内容中包含转义符号("\"")
    -- follows a typical string: "This is \"great\"!"
    -- 处理这种情况的方法之一是先对文本进行预处理，将所有可能导致歧义的内容编码为别的内容
    -- 例如可以将 "\"" 编码为 "\1"。不过,如果原文本来就含有"\1",那么就会遇到问题
    -- 另一种可以避免这个问题的简单做法是将所有的\x编码为\ddd 其中ddd为字符x的十六进制表现形式
    do
        function code(s)
            return (string.gsub(s,"\\(.)",function (x)
                return string.format("\\%03d",string.byte(x))
            end))
        end
    end
    -- 这样，由于原字符串中所有的 "\ddd" 都进行了编码，所以编码后字符串中的 "\ddd" 序列一定都是编码造成的，这样解码也很简单
    do
        function decode(s)
            return (string.gsub(s,"\\(%d%d%d)",function (d)
                return "\\" .. string.char(tonumber(d).tonumber)
            end))
        end
    end

    -- 现在我们就可以完成把一个字符串中被双引号"引起来的内容改为大写的需求
    -- 由于编码后的字符串中不包含任何转义的引号("\""),所以就可以直接使用".-"来查找位于一对引号的内容
    do
        local s = [[follows a typical string: "This is \"great\"!".]]
        s = code(s)
        s = string.gsub(s,'".-"',string.upper)
        s = decode(s)
        print(s)
        -- 上述代码等同于
        print(decode(string.gsub(code(s),'".-"',string.upper)))
    end

    -- 是否能将模式匹配函数用于UTF-8字符串取决于模式本身
    -- 由于UTF-8的主要特性之一就是任意字符的编码不会出现在别的字符的编码中，因此文本类的模式一般可以正常工作
    -- 字符分类和字符集只对ASCII字符有效
    -- 例如可以对UTF-8字符串使用模式'%s',但它只能匹配ASCII空格，而不能匹配诸如HTML空格等等其他的Unicode空格

    -- 恰当的模式能够为处理Unicode带来额外的能力
    -- 一个优秀的例子是预定义模式utf8.charpattern，该模式只精确的匹配一个UTF-8字符
    -- utf8标准库中就是按照下面的方法定义这个模式的:
    -- utf8.charpattern = [\0-\x7F\xC2-\xF4][\x80-\xBF]
    -- 该模式的第一部分匹配ASCII字符(范围[0,0x7F])或多字节序列的起始字节(范围[0xC2,0xF4]),第二部分则匹配零个或多个后续的字节(范围[0x80,0xBF])
--]]